goog.provide('ptucker.Chat');

goog.require('goog.Timer');
goog.require('goog.Uri.QueryData');
goog.require('goog.debug.FancyWindow');
goog.require('goog.debug.Logger');
goog.require('goog.dom');
goog.require('goog.json');
goog.require('goog.net.EventType');
goog.require('goog.net.XhrIo');
goog.require('goog.structs.Set');
goog.require('goog.style');

// TODO(ptucker) test timeout, pool xhrs? append messages to pending and remove
//     each as response is returned
// TODO(ptucker) user submit

/**
 * @param {object} user { userId, email, nickName }
 * @param {object} room { id, name }
 * @param {?Array<object>} opt_msgs [{seq, timeMillis, author, content}, ...]
 * @constructor
 */
ptucker.Chat = function(user, room, opt_msgs) {
  this.logger_.info('init user|room.id|msgs.length=' + user + '|' + room.id +
      '|' + msgs.length);

  /**
   * @type {?object}
   * @private
   */
  this.user_ = user;

  /**
   * @type {?object}
   * @private
   */
  this.room_ = room;

  /**
   * Set of message sequence #s we've displayed.
   * @type {goog.structs.Set.<number>}
   * @private
   */
  this.msgSeqs_ = new goog.structs.Set();

  this.displayPresence_(room['presence']);
  if (opt_msgs) {
    this.displayMsgs_(opt_msgs);
  }

  /**
   * @type {goog.Timer}
   * @private
   */
  this.timer_ = new goog.Timer(/* 60 secs */ 60000);
  this.timer_.addEventListener(goog.Timer.TICK,
      goog.bind(this.heartbeat_, this));
  this.timer_.start();
};

/**
 * @type {goog.debug.Logger}
 * @private
 */
ptucker.Chat.prototype.logger_ = goog.debug.Logger.getLogger('ptucker.Chat');

/**
 * @type {number}
 * @private
 */
ptucker.Chat.prototype.maxSeq_ = 0;

/**
 * Singleton instance.
 * @type {ptucker.Chat}
 * @private
 */
ptucker.Chat.chat_ = null;

/**
 * @param {object} user { userId, email, nickName }
 * @param {object} room { id, name }
 * @param {?Array<object>} opt_msgs [{seq, timeMillis, author, content}, ...]
 */
ptucker.Chat.init = function(user, room, opt_msgs) {
  ptucker.Chat.chat_ = new ptucker.Chat(user, room, opt_msgs);
  var debugWindow = new goog.debug.FancyWindow('ptucker.Chat');
  debugWindow.setEnabled(true);
  debugWindow.init();
};

/**
 * @param {Array<Object>} presence Array of {userId, nickName, email}.>
 */
ptucker.Chat.prototype.displayPresence_ = function(presence) {
  var totalCount = presence.length;
  var displayCount = 0;
  var title = '';
  var thisUserId = this.user_['userId'];
  for (var i = 0; i < totalCount; ++i) {
    var user = presence[i];
    if (thisUserId == user['userId']) {
      --totalCount;
    } else {
      if (displayCount < 3) {
        if (displayCount > 0) {
          title += ', ';
        }
        title += user['nickName'];
        ++displayCount;
      }
    }
  }
  if (displayCount >= 3) {
    title += ' and ' + (totalCount - displayCount) + ' more';
  }
  var titleElem = goog.dom.$('title');
  goog.dom.removeChildren(titleElem);
  goog.dom.appendChild(titleElem, goog.dom.createTextNode(title));
};

/**
 * @param {Array.<object>} msgs [ { seq, timeMillis, author, content }, ... ]
 */
ptucker.Chat.prototype.displayMsgs_ = function(msgs) {
  this.logger_.info('displayMsgs_ msgs.length=' + msgs.length);

  var maxSeq = this.maxSeq_;
  var user = this.user_;

  // msgs
  for (var i = msgs.length - 1; i >= 0; --i) {
    var msg = msgs[i];
    var seq = msg['seq'];
    if (!this.msgSeqs_.contains(seq)) {
      maxSeq = Math.max(maxSeq, seq);
      var author = msg['author'];
      var authorString = author ?
          author['userId'] == user['userId'] ?
              'me' :
              author['nickName'] || author['email'] :
          'anonymous';
      var content = msg['content'];
      if (content) {
        goog.dom.$('msgs').appendChild(this.newMsgElem_('div',
            authorString, content));
      }
      this.msgSeqs_.add(seq)
    }
  }

  this.maxSeq_ = maxSeq;
};

/**
 * @param {string} tagName Element in which to wrap message.
 * @param {string} author Author of message.
 * @param {string} content Content of message.
 * @return {Element} New element.
 */
ptucker.Chat.prototype.newMsgElem_ = function(tagName, authorString, content) {
  return goog.dom.createDom(tagName, null,
      goog.dom.createDom('b', null,
          goog.dom.createTextNode(authorString + ' - ')),
      goog.dom.createDom('span', null,
          goog.dom.createTextNode(content)));
};

/**
 * @private
 */
ptucker.Chat.prototype.heartbeat_ = function() {
  this.logger_.info('heartbeat_');
  this.request_();
};

/**
 * @param {string} content Message content.
 */
ptucker.Chat.prototype.send = function() {
  this.logger_.info('send');
  var content = goog.dom.$('compose').value;
  if (content) {
    var user = this.user_;

    goog.dom.$('pending-content').appendChild(
        chat.newMsgElem_('span', 'me', content));
    goog.style.showElement(goog.dom.$('pending'), true);

    this.request_(content);
  }
};

/**
 * Mockable for tests.
 * @return {goog.net.XhrIo} New XHR object.
 */
ptucker.Chat.prototype.newXhr_ = function() {
  return new goog.net.XhrIo();
};

/**
 * @param {?string} opt_content Content of new chat message.
 * @private
 */
ptucker.Chat.prototype.request_ = function(opt_content) {
  this.logger_.info('request_ ' + opt_content);

  var xhr = this.newXhr_();
  xhr.addEventListener(
      [goog.net.EventType.COMPLETE, goog.net.EventType.TIMEOUT],
      goog.bind(this.onResponse_, this));
  var queryData = new goog.Uri.QueryData();
  queryData.set('seq', this.maxSeq_);
  queryData.set('room', this.room_['id']);
  if (opt_content) {
    queryData.set('msg', opt_content);
  }
  xhr.send('/chat', 'POST', queryData.toString());
};

/**
 * @param {goog.net.Event} event Response object.
 * @private
 */
ptucker.Chat.prototype.onResponse_ = function(event) {
  this.logger_.info('onResponse_');

  goog.style.showElement(goog.dom.$('pending'), false);
  goog.dom.removeChildren(goog.dom.$('pending-content'));

  var xhr = event.target;
  if (goog.net.EventType.COMPLETE == event.type && xhr && xhr.isSuccess()) {
    var response = goog.json.parse(xhr.getResponseText());
    var room = response['room'];
    this.displayPresence_(room && room['presence']);
    var msgs = response['msgs'];
    if (msgs) {
      this.displayMsgs_(msgs);
    }
  } else {
    this.logger_.severe('response error type|xhr=' + event.type + '|' +
        xhr && xhr.getStatus());
  }

  if (xhr) {
    xhr.dispose();
  }
};

goog.exportSymbol('ptucker', ptucker);
goog.exportProperty(ptucker, 'Chat', ptucker.Chat);
goog.exportProperty(ptucker.Chat, 'init', ptucker.Chat.init);
goog.exportProperty(ptucker.Chat, 'send', ptucker.Chat.send);
